using System;
using System.Collections.Generic;
using System.Reflection;
#if TEST
using Xunit;
#endif

namespace Fadd.Components
{
    /// <summary>
    /// Thrown when we cant find all parameters for a component.
    /// </summary>
    public class MissingConstructorException : Exception
    {
        private readonly Type _type;
        private readonly Dictionary<string, object> _param;

		/// <summary>
		/// Initializes a new instance of the <see cref="MissingConstructorException"/> class.
		/// </summary>
		/// <param name="type">Type that is missing a public constructor.</param>
		public MissingConstructorException(Type type) : base("Could not find a public constructor for '" + type.FullName + "'.")
		{
			_type = type;
			_param = new Dictionary<string, object>();
		}

        /// <summary>
		/// Initializes a new instance of the <see cref="MissingConstructorException"/> class.
        /// </summary>
        /// <param name="type">Type that we are missing parameters for.</param>
        /// <param name="parameters">Parameters that have been specified.</param>
        public MissingConstructorException(Type type, Dictionary<string, object> parameters)
			: base("Failed to find all constructor parameters for '" + type.FullName + "'.")
        {
            _type = type;
            _param = parameters;
        }

        /// <summary>
		/// Initializes a new instance of the <see cref="MissingConstructorException"/> class.
        /// </summary>
        /// <param name="type">Type that we are missing parameters for.</param>
        /// <param name="missingParameters">The parameters missing for specified constructors</param>
        /// <param name="suppliedParameters">Parameters supplied through configuration</param>
        public MissingConstructorException(Type type, Dictionary<string, object> suppliedParameters, ICollection<KeyValuePair<ConstructorInfo, ParameterInfo>> missingParameters)
            : base(BuildMessage(type, missingParameters))
        {
            Check.Min(1, missingParameters.Count, "At least one constructor must be specified as missing parameters");
            _type = type;
            _param = suppliedParameters;
        }

        private static string BuildMessage(Type type, ICollection<KeyValuePair<ConstructorInfo, ParameterInfo>> missingParameters)
        {
			if (missingParameters.Count == 0)
				return "Failed to find a public constructor for '" + type.FullName + "'.";

            string message = string.Empty;
            foreach (KeyValuePair<ConstructorInfo, ParameterInfo> pair in missingParameters)
            {
                message += Compiler.GetTypeName(pair.Key.DeclaringType, false) + "(";
                foreach (ParameterInfo info in pair.Key.GetParameters())
                    message += Compiler.GetTypeName(info.ParameterType, false) + " " + info.Name + ", ";
                message = message.TrimEnd(',', ' ') + ")";
                message += Environment.NewLine + "\tMissing: " + Compiler.GetTypeName(pair.Value.ParameterType, false) + " " + pair.Value.Name + Environment.NewLine;
            }

            return message;
        }

        #region BuildMessage test

#if TEST
        /// <summary>
        /// Test to make sure the BuildMessage gives a correct output
        /// </summary>
        [Fact]
        private static void TestBuildMessage()
        {
            ConstructorInfo constructorInfo = typeof(MissingConstructorException).GetConstructors()[0];
            Dictionary<ConstructorInfo, ParameterInfo> missingParameters = new Dictionary<ConstructorInfo, ParameterInfo>();
            missingParameters.Add(constructorInfo, constructorInfo.GetParameters()[1]);

            MissingConstructorException exception = new MissingConstructorException(typeof(MissingConstructorException), null, missingParameters);
            Assert.Equal(@"MissingConstructorException(Type type, Dictionary<String, Object> param)
	Missing: Dictionary<String, Object> param
", exception.Message);
        }
#endif

        #endregion

        /// <summary>
        /// Type that we are missing parameters for.
        /// </summary>
        public Type InterfaceType
        {
            get { return _type; }
        }

        /// <summary>
        /// Parameters that have been specified.
        /// </summary>
        public Dictionary<string, object> SuppliedParameters
        {
            get { return _param; }
        }
    }
}
